import { Command } from "commander";
import { 
    ILoggable, Log, 
    IAnalyzer, ArkCompareAnalyzer, ArkCmpCfg, defaultArkCmpCfg,
    IOutput, OutputCfg, OutputFactory, OutputType,
    JsonReader,
    ConsoleData,
    JsonData,
    TextData,
    HtmlData
} from "@meminsight/core";
import { IProcess } from "./IProcess";
import { Version } from "../Version";
import { outputFile } from "fs-extra";

/**
 * Ark Compare Process
 */
export class ArkCompareProc implements IProcess, ILoggable { 
    public readonly DOMAIN: string = 'meminsight';
    public readonly TAG: string = ArkCompareProc.name;
    public static readonly CMD: string = 'ark-compare';
    public static readonly DESC: string = "ark compare process.";
    public static readonly VER: string = Version.ARK_COMPARE.toString();
    private static _instance: ArkCompareProc; 
    private _analyzer: IAnalyzer = new ArkCompareAnalyzer();

    private constructor() {}

    /**
     * get instance
     * @returns instance
     */
    public static instance(): ArkCompareProc {
        if (!ArkCompareProc._instance) {
            ArkCompareProc._instance = new ArkCompareProc();
        }
        return ArkCompareProc._instance;
    }

    public initialize(proc: Command, any: any) {
        proc.command(ArkCompareProc.CMD)
            .description(ArkCompareProc.DESC)
            .requiredOption("-s, --source <file>", "source heapsnapshot file")
            .requiredOption("-t, --target <file>", "target heapsnapshot file")
            .option("-n, --node_name <node name>", undefined)
            .option<number>("-S, --sort_type <sort type: 0 - count | 1 - self_size | 2 - retained_size>", "sort type", parseInt, 0)
            .option("-c, --config <configuration file>", "configuration file")
            .option("-o, --output <file>", "output file")
            .option("-f, --output_file <file type>", "output file type, console | text | json | html.")
            .action(async (options) => {
                await ArkCompareProc.instance().process(new Map<string, any>([
                    ["source", options.source],
                    ["target", options.target || undefined],
                    ["node_name", options.node_name || undefined],
                    ["sort_type", options.sort_type || "count"],
                    ["config", options.config || undefined],
                    ["output", options.output],
                    ["output_file", options.output_file],
                ]));
            });
    }

    private async process(args: Map<string, any>): Promise<void> {
        let source = args.get("source");
        if (!source) {
            Log.errorX(this, "Source filepath is required");
            return;
        }
        let target = args.get("target");
        if (!target) {
            Log.errorX(this, "Target filepath is required");
            return;
        }
        let result = await this._analyzer.analyze([source, target]);
        if (!result) {
            Log.errorX(this, "Faled to analyze.");
            return
        }
        Log.infoX(this, "Analyzed successfully.");

        let cfgfile = args.get('config');
        let setResult: boolean = false;
        if (cfgfile) {
            let cfg = await JsonReader.instance().readFromFile<ArkCmpCfg>(cfgfile);
            if (cfg) {
                this._analyzer.setConfig(cfg);
                setResult = true;
            }
        }
        if (!setResult) {
            Log.warnX(this, "No configuration found, use default config.");
            this._analyzer.setConfig(defaultArkCmpCfg());
        }
        console.log(this._analyzer.getConfig());
        Log.infoX(this, "Set configuration successfully.");

        let sort_type = args.get("sort_type") || 0;
        let nodeName = args.get("node_name");
        let outputDir: string = args.get("output") || './output';
        let outputFileType: string = args.get("output_file") || 'console';
        Log.infoX(this, `output:${outputDir}; type:${outputFileType}`);

        this.takeAction(sort_type, outputDir, outputFileType, nodeName)
    }

    private async getOutputCfg(
        output: IOutput, 
        outputDesc: any, 
        outputDir: string, 
        outputFile: string) : Promise<OutputCfg> { 
        const analyzerCfg = this._analyzer.getConfig() as ArkCmpCfg;
        let cfg: OutputCfg = {
            desc: outputDesc,
            output: {
                dir: outputDir,
                filename: outputFile,
                extension: output.type.toString(),
            }
        }
        return cfg;
    }

    private async outputResults(
        results: Array<any>, 
        outputEntity: IOutput, 
        outputCfg: OutputCfg): Promise<void> {
        if (outputEntity.type === OutputType.CONSOLE) {
            let data = new ConsoleData(false, results);
            outputEntity.output(data, outputCfg);
        } else if (outputEntity.type === OutputType.JSON) {
            let data = new JsonData(outputCfg.desc, results);
            outputEntity.output(data, outputCfg);
        } else if (outputEntity.type === OutputType.TEXT) {
            let data = new TextData(outputCfg.desc, JSON.stringify(results));
            outputEntity.output(data, outputCfg);
        } else if (outputEntity.type === OutputType.HTML) {
            let data = new HtmlData(outputCfg.desc, JSON.stringify(results));
            outputEntity.output(data, outputCfg);    
        } else {
            Log.errorX(this, 'Invalid output file type.');
        }
    }

    private async takeAction(sort_type: number, outputDir:string, output_type: string, node_name?: string): Promise<void> {
        const analyzer = this._analyzer as ArkCompareAnalyzer;
        const data_tmp = await analyzer.diffSnapshots(node_name);
        const results = await analyzer.sortDiffs(sort_type, data_tmp);
        let outputEntity: IOutput = OutputFactory.getOutput(output_type);
        let outputCfg = await this.getOutputCfg(outputEntity, undefined, outputDir, '');
        outputCfg.desc = { 
            "SourceSnapshot": analyzer.srcSnapshotFilePath, 
            "TargetSnapshot": analyzer.dstSnapshotFilePath,
            "Action": "Compare Snapshots",
            "NodeName": node_name || "None",
            "Time": Date.now()
        };
        this.outputResults(results, outputEntity, outputCfg);
    }
}